So, hier es ein weiteres schnelles Tutorial, das Ihnen zeigt, wie man die FreeType Font Rendering Library in OpenGL verwendet. Indem wir die FreeType Library verwenden, können wir Anti-Aliased Text erzeugen, der besser als der Text aussieht, der Bitmap Fonts (wie in Lektion 13) benutzt. Unser Text wird auch noch ein paar andere Vorteile gegenüber von Bitmap Fonts hat - er wird einfach zu rotieren sein und wird ebenfalls mit den OpenGL Picking-Funktionen funktionieren.
Motivation
Hier habe ich den selben Text ausgedruckt, der einmal beide WGL Bitmap Fonts verwendet und Fonts, die mit FreeType gerendert sind (beide sind Arial Black Italic).
C:\PROGRAM FILES\GNUWIN32\INCLUDE\FREETYPE2
C:\PROGRAM FILES\GNUWIN32\INCLUDE
C:\PROGRAM FILES\GNUWIN32\LIB
#ifndef FREE_NEHE_H #define FREE_NEHE_H // FreeType Header #include < ft2build.h> #include < freetype/freetype.h> #include < freetype/ftglyph.h> #include < freetype/ftoutln.h> #include < freetype/fttrigon.h> // OpenGL Header #include < windows.h> // (wird von den GL Headern benötigt) #include < GL/gl.h> #include < GL/glu.h> // einige STL Header #include < vector> #include < string> // Indem wir die STL Exception Library verwenden, erhöhen wir die // Chancen, dass jemand, der unseren Code verwendet, alle Exceptions // die wir werfen, korrekt abfängt. #include < stdexcept> // MSVC wird eine Menge unbrauchbarer Warnungen ausspucken, wenn Sie // Vectors von Strings erzeugen, dieses Pragma schaltet das ab. #pragma warning(disable: 4786)
// Wrappe alles in einem Namespace, So können wir gängige
// Funktionsnamen wie "print" benutzen, ohne uns Sorgen darum
// zu machen, irgendwelchen Code zu überlappen.
namespace freetype {
// Innerhalb dieses Namespaces, geben wir uns die Möglichkeit
// einfach "vector" zu schreiben, anstatt von "std::vector"
using std::vector;
// Das Selbe für String.
using std::string;
// Dies enthält alle relevanten Informationen über jeglichen
// FreeType Font, den wir erzeugen wollen.
struct font_data {
float h; // enthält die Höhe des Fonts.
GLuint * textures; // enthält die Textur Id's
GLuint list_base; // enthält die erste Display Listen Id
// Die Init Function wird ein Font mit
// der Höhe h aus der Datei fname erzeugen.
void init(const char * fname, unsigned int h);
// gebe alle Ressourcen, die mit dem Font verbunden sind, frei.
void clean();
};
// Die Flaggschiff-Funktion der Library - Diese wird unseren Text // an den Fenster-Koordinaten X, Y ausgeben und dabei den Font ft_font verwenden. // Die aktuelle Modelview Matrix wird ebenso auf den Text angewandt. void print(const font_data &ft_font, float x, float y, const char *fmt, ...); } // Schließe den Namespace #endif
// Inkludiere unsere Header Datei.
#include "freetype.h"
namespace freetype {
// Diese Funktion ermittelt die erste 2er-Basis >= der Int, den
// wir übergeben.
inline int next_p2 (int a )
{
int rval=1;
// rval<<=1 ist ein schönerer Weg rval*=2; zu schreiben
while(rval< a) rval<<=1;
return rval;
}
// erzeuge eine Display-Liste für das entsprechende Zeichen.
void make_dlist ( FT_Face face, char ch, GLuint list_base, GLuint * tex_base ) {
// Als erstes holen wir FreeType um unser Zeichen in ein
// Bitmap zu rendern. Das benötigt ein paar FreeType Befehle:
// Lade den Glyphen unseres Zeichens.
if(FT_Load_Glyph( face, FT_Get_Char_Index( face, ch ), FT_LOAD_DEFAULT ))
throw std::runtime_error("FT_Load_Glyph failed");
// bewege die Frontseite des Glyphen in ein Glyph Objekt.
FT_Glyph glyph;
if(FT_Get_Glyph( face->glyph, &glyph ))
throw std::runtime_error("FT_Get_Glyph failed");
// konvertiere den Glyphen in ein Bitmap.
FT_Glyph_To_Bitmap( &glyph, ft_render_mode_normal, 0, 1 );
FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph;
// Diese Referenz wird es einfacher machen, auf das Bitmap zuzugreifen.
FT_Bitmap& bitmap=bitmap_glyph->bitmap;
// Benutze unsere Helfer-Funktion, um die Breiten der
// Bitmap Daten zu ermitteln, die wir benötigen, um unsere
// Texturen zu erzeugen.
int width = next_p2( bitmap.width );
int height = next_p2( bitmap.rows );
// Alloziere Speicher für die Textur-Daten.
GLubyte* expanded_data = new GLubyte[ 2 * width * height];
// Hier füllen wir die Daten für das erweiterte Bitmap.
// Beachten Sie, dass wir ein zwei Channel Bitmap verwenden (einen
// Durchsichtigkeit und einen für Alpha), aber wir wenden beide
// Durchsichtigkeit und Alpha auf den Wert an, den wir im
// FreeType Bitmap finden.
// Wir verwenden den ?: Operator, um mitzuteilen, dass wir den Wert, den wir verwenden
// entweder 0 sein wird, wenn wir in der Auffüll-Zone sind oder aber,
// dass es ansonsten der Wert aus dem FreeType Bitmap sein wird.
for(int j=0; j < height;j++) {
for(int i=0; i < width; i++){
expanded_data[2*(i+j*width)]= expanded_data[2*(i+j*width)+1] =
(i>=bitmap.width || j>=bitmap.rows) ?
0 : bitmap.buffer[i + bitmap.width*j];
}
}
// Nun initialisieren wir einfach ein Paar Textur Parameter. glBindTexture( GL_TEXTURE_2D, tex_base[ch]); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // Hier erzeugen wir die eigentlich Textur, beachten Sie, // dass wir GL_LUMINANCE_ALPHA verwenden, um zu indizieren, dass // wir 2 Channel Daten verwenden. glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data ); // Nachdem wir die Textur erzeugt haben, benötigen wir die erweiterten Daten nicht mehr. delete [] expanded_data;
// Nun erzeugen wir die Display Liste glNewList(list_base+ch,GL_COMPILE); glBindTexture(GL_TEXTURE_2D,tex_base[ch]); // als erstes müssen wir uns etwas nach oben bewegen, so dass // die Zeichen die richtige Menge Platz zwischen // und vor den Zeichen haben. glTranslatef(bitmap_glyph->left,0,0); // Nun bewegen wir uns etwas nach unten, für den Fall, dass // das Bitmap über die untere Linie geht // Das ist nur für Zeichen wir 'g' oder 'y' der Fall. glPushMatrix(); glTranslatef(0,bitmap_glyph->top-bitmap.rows,0); // Nun müssen wir etwas korrigieren, da vieles unserer Textur // einfach nur leerer aufgefüllter Platz ist. // Wir finden heraus, welche Teile der Textur vom eigentlichen // Zeichen verwendet werden und speichern die Informationen in den // Variablen x und y, dann zeichnen wir den Quad // Quad, wir werden nur die Teile der Textur referenzieren, welche // das eigentliche Zeichen enthält. float x=(float)bitmap.width / (float)width, y=(float)bitmap.rows / (float)height; // Hier zeichnen wir die texturierten Quads. // Das Bitmap welches wir von FreeType erhalten haben, ist nicht ganz // so ausgerichtet wie wir es gerne hätten, // aber wir linken die Textur auf den Quad // Auf diese Weise wird das Ergebniss korrekt ausgerichtet. glBegin(GL_QUADS); glTexCoord2d(0,0); glVertex2f(0,bitmap.rows); glTexCoord2d(0,y); glVertex2f(0,0); glTexCoord2d(x,y); glVertex2f(bitmap.width,0); glTexCoord2d(x,0); glVertex2f(bitmap.width,bitmap.rows); glEnd(); glPopMatrix(); glTranslatef(face->glyph->advance.x >> 6 ,0,0); // Inkrementiere die Raster Position so, als ob wir ein Bitmap Font wären. // (wird nur benötigt, wenn Sie die Text Länge berechnen wollen) // glBitmap(0,0,0,0,face->glyph->advance.x >> 6,0,NULL); // beende die Display List glEndList(); }
void font_data::init(const char * fname, unsigned int h) {
// Alloziere etwas Speicher um die Textur Ids zu speichern.
textures = new GLuint[128];
this->h=h;
// erzeuge und initialisiere eine FreeType Font Library.
FT_Library library;
if (FT_Init_FreeType( &library ))
throw std::runtime_error("FT_Init_FreeType failed");
// Das Objekt in welchem FreeType die Informationen über den
// Font speichert, wird ein "face" (eine Seite/Gesicht...) genannt.
FT_Face face;
// Hier laden wir die Font Informationen aus der Datei.
// Von allen Möglichkeiten, wo der Code fehl schlagen könnte, ist die hier die wahrscheinlichste,
// da FT_New_Face fehl schlagen wird, wenn die Font Datei nicht existiert oder irgendwie beschädigt ist.
if (FT_New_Face( library, fname, 0, &face ))
throw std::runtime_error("FT_New_Face failed (there is probably a problem with your font file)");
// aus unerklärlichen Gründen ist die Masseinheit der Font Größe in Freetype in
// 1/64tel Pixeln angegeben. Deshalb müssen wir, wenn wir einen Font
// h Pixel hoch haben wollen, die Größe auf h*64 anpassen.
// (h << 6 ist lediglich ein hübscherer Weg h*64 zu schreiben)
FT_Set_Char_Size( face, h << 6, h << 6, 96, 96);
// hier fragen wir OpenGL an, Ressourcen für all die
// Texturen und Display-Listen die wir
// erzeugen wollen, zu allozieren.
list_base=glGenLists(128);
glGenTextures( 128, textures );
// Hier erzeugen wir die einzelnen Fonts Display-Listen.
for(unsigned char i=0;i<128;i++)
make_dlist(face,i,list_base,textures);
// Nun, wo die Display Liste erzeugt wurde, benötigen wir die Face Informationen
// nicht mehr, weshalb wir die damit verbundenen Ressourcen wieder freigeben.
FT_Done_Face(face);
// Ditto für die Font Library.
FT_Done_FreeType(library);
}
void font_data::clean() {
glDeleteLists(list_base,128);
glDeleteTextures(128,textures);
delete [] textures;
}
// Eine ziemlich einfache Funktion, die eine Projektions Matrix
// pusht, um die Objekt-Welt-Koordinaten identisch
// mit den Fenster-Koordinaten zu machen.
inline void pushScreenCoordinateMatrix() {
glPushAttrib(GL_TRANSFORM_BIT);
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluOrtho2D(viewport[0],viewport[2],viewport[1],viewport[3]);
glPopAttrib();
}
// Poppt die Projektions Matrix, ohne den aktuellen
// MatrixMode zu ändern.
inline void pop_projection_matrix() {
glPushAttrib(GL_TRANSFORM_BIT);
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glPopAttrib();
}
// fast das gleiche wie NeHe's glPrint Funktion, aber etwas modifiziert,
// damit sie auch mit FreeType Fonts funktioniert.
void print(const font_data &ft_font, float x, float y, const char *fmt, ...) {
// Wir wollen ein Koordinaten-System, wo die Entfernung in Fenster-Pixeln gemessen wird.
pushScreenCoordinateMatrix();
GLuint font=ft_font.list_base;
// Wir machen die Höhe etwas größer. So wird es etwas Abstand zwischen den Zeilen geben.
float h=ft_font.h/.63f;
char text[256]; // enthält unseren String
va_list ap; // Zeiger auf die Argumentenliste
if (fmt == NULL) // Wenn da kein Text ist,
*text=0; // mache nichts
else {
va_start(ap, fmt); // Parse den String für Variablen
vsprintf(text, fmt, ap); // und konvertiere Symbole in die tatsächlichen Zahlen
va_end(ap); // Das Ergebniss wir in Text gespeichert
}
// hier kommt etwas Code, um den Text den wir bekommen haben,
// in mehrere Zeilen zu splitten.
// Das könnte wesentlich eleganter gemacht werden, indem eine
// Regular Expression Library benutzt werden würde, so wie sie unter
// boost.org verfügbar ist (Ich habe das nun nur von Hand gemacht, um Komplikationen
// in diesem Tutorial mit unnötigen Library-Abhängikeiten zu vermeiden).
const char *start_line=text;
vector< string> lines;
for(const char *c=text;*c;c++) {
if(*c=='\n') {
string line;
for(const char *n=start_line;n< c;n++) line.append(1,*n);
lines.push_back(line);
start_line=c+1;
}
}
if(start_line) {
string line;
for(const char *n=start_line;n< c;n++) line.append(1,*n);
lines.push_back(line);
}
glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT | GL_TRANSFORM_BIT);
glMatrixMode(GL_MODELVIEW);
glDisable(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glListBase(font);
float modelview_matrix[16];
glGetFloatv(GL_MODELVIEW_MATRIX, modelview_matrix);
// Hier wird der Text angezeigt.
// Für jede Textzeile resetten wir die Modelview Matrix,
// So dass die Textzeilen an der korrekten Position starten.
// Beachten Sie, dass wir die Matrix resetten müssen, anstatt nur auf h runter zu translatieren.
// Das kommt daher, weil jedes Zeichen, dass gezeichnet wird
// die aktuelle Matrix ändert, so dass das nächste Zeichen
// direkt dahinter gezeichnet wird.
for(int i=0;i< lines.size();i++) {
glPushMatrix();
glLoadIdentity();
glTranslatef(x,y-h*i,0);
glMultMatrixf(modelview_matrix);
// Der auskommentierte Raster-Positions-Kram kann nützlich sein, wenn Sie
// wissen müssen, wie lang der Text ist, den Sie erzeugen.
// Wenn Sie sich dazu entscheiden, ihn zu benutzen, stellen Sie sicher, dass Sie auch den glBitmap Befehl wieder in
// make_dlist() verwenden und dieser nicht auskommentiert bleibt.
// glRasterPos2f(0,0);
glCallLists(lines[i].length(), GL_UNSIGNED_BYTE, lines[i].c_str());
// float rpos[4];
// glGetFloatv(GL_CURRENT_RASTER_POSITION ,rpos);
// float len=x-rpos[0]; (nimmt an, dass keine Rotation gemacht wurde)
glPopMatrix();
}
glPopAttrib();
pop_projection_matrix();
}
} // Schließe den Namespace
#include "freetype.h" // Header für unsere kleine Font Library.
// Dies enthält all die Informationen für unseren Font, den wir erzeugen werden. freetype::font_data our_font;
our_font.init("Test.ttf", 16); // erzeuge den FreeType Font
our_font.clean();
int DrawGLScene(GLvoid) // Hier kommt der ganze Zeichnen-Kram hin
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // löscht Screen und Depth Buffer
glLoadIdentity(); // Resettet die aktuelle Modelview Matrix
glTranslatef(0.0f,0.0f,-1.0f); // bewege eine Einheit in den Screen hinein
// Blauer Text
glColor3ub(0,0,0xff);
// Positioniere den WGL Text auf dem Screen
glRasterPos2f(-0.40f, 0.35f);
glPrint("Active WGL Bitmap Text With NeHe - %7.2f", cnt1); // gebe GL Text auf dem Screen aus
// Hier geben wir etwas Text aus, unter der Benutzung unseres Freetype Fonts
// Der einzig wirklich wichtige Befehl ist der eigentliche Print() Aufruf,
// Aber um die Resultate etwas interessanter zu gestalten,
// Habe ich etwas Code für Rotation und Skalierung des Textes eingefügt.
// Roter Text
glColor3ub(0xff,0,0);
glPushMatrix();
glLoadIdentity();
glRotatef(cnt1,0,0,1);
glScalef(1,.8+.3*cos(cnt1/5),1);
glTranslatef(-180,0,0);
freetype::print(our_font, 320, 200, "Active FreeType Text - %7.2f", cnt1);
glPopMatrix();
// Um die Fähigkeit von Print zu testen, wie sie mit neuen Zeilen umgeht, fügen Sie die folgende Kommentarzeile in den Code ein.
// freetype::print(our_font, 320, 200, "Here\nthere\nbe\n\nnewlines\n.", cnt1);
cnt1+=0.051f; // Inkrementiere den ersten Zähler
cnt2+=0.005f; // Inkrementiere den zweiten Counter
return TRUE; // Alles verlief OK
}
MSG msg; // Windows Nachrichten Struktur
BOOL done=FALSE; // Bool Variable um die Schleife zu beenden
try { // benutze Exception Handling
// Shutdown
KillGLWindow(); // Kill das Fenster
// Catche jegliche Exception, die geworfen wurde
} catch (std::exception &e) {
MessageBox(NULL,e.what(),"CAUGHT AN EXCEPTION",MB_OK | MB_ICONINFORMATION);
}
return (msg.wParam); // beende das Programm
}